package com.agg.persist.xml;

import com.agg.core.utils.Asserts;
import com.agg.persist.xml.loader.ClassPathResourceLoader;
import com.agg.persist.xml.mapping.Property;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class XmlBuildFactoryTemplate {
	private boolean validating = false;
	private boolean namespaceAware = false;
	private String path;
	protected final Log logger = LogFactory.getLog(getClass());
	/**
	 * @return the namespaceAware
	 */
	public boolean isNamespaceAware() {
		return namespaceAware;
	}

	/**
	 * @param namespaceAware the namespaceAware to set
	 */
	public void setNamespaceAware(boolean namespaceAware) {
		this.namespaceAware = namespaceAware;
	}

	/**
	 * @return the validating
	 */
	public boolean isValidating() {
		return validating;
	}

	/**
	 * @param validating the validating to set
	 */
	public void setValidating(boolean validating) {
		this.validating = validating;
	}


	/**
	 * initialize a factory with list of paths
	 * @param path the list of paths to read xml files.
	 */
	public boolean initFactory(String path){
		try {

			ClassPathResourceLoader loader = new ClassPathResourceLoader(this.getClass());
			loader.loadResource(is->readXmlStream(is), path);
			return true;
		} catch (IOException e) {
			logger.error("no found the file");
			return false;
		}
	}

	/**
	 * reload the factory that read xml files again.
	 */
	public void reloadFactory(){
		initFactory(this.path);
	}

	/**
	 * read the xml input stream, and then call <code>buildFactory(Element)</code>
	 * @param inputStream
	 */
	protected void readXmlStream(InputStream inputStream) {
		try {
			if(inputStream==null) throw new RuntimeException("no input stream");
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        factory.setValidating(this.isValidating());;
	        factory.setNamespaceAware(this.isNamespaceAware());
	        DocumentBuilder build = factory.newDocumentBuilder();
			build.setEntityResolver(new EntityResolver() {
				public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
					Class clazz=XmlBuildFactoryTemplate.class;
					ClassPathResource resource = new ClassPathResource("entity-mapper.dtd");
					InputStream is = resource.getInputStream();
					return new InputSource(is);
				}
			});
	        Document doc = build.parse(new InputSource(inputStream));
	        Element root = doc.getDocumentElement();
	        buildFactory(root);
		} catch (IOException | ParserConfigurationException | SAXException e) {
			throw new RuntimeException("Error when decode xml stream by sax", e);
		}

	}

	/**
	 * read from xml and build the factory.
	 * @param root the root of the xml
	 */
	protected void buildFactory(Element root) {

		NodeList nodeList = root.getChildNodes();
		List<Element> propGroupEles= new ArrayList<>();
		List<Element> beanEles= new ArrayList<>();
		//获取属性集合和实体集合的元素
		for(int i=0; i<=nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if(!(node instanceof Element)) continue;
			Element element = (Element) node;
			if(element.getTagName().equals("property-group")){
				propGroupEles.add(element);
			}else if(element.getTagName().equals("entity-mapper")){
				beanEles.add(element);
			}
		}
		if(propGroupEles.isEmpty()){
			for(Element element:beanEles) {
				loadBean(element);
			}
		}else{
			//加载属性集合
			Map<String,List<Property>> group=loadPropGroup(propGroupEles);
			for(Element element:beanEles) {
				loadBean(element,group);
			}
		}

	}

	/**
	 * define what to do with each of node.
	 * @param element
	 */
	protected abstract void loadBean(Element element);

	/**
	 * define what to do with each of node.
	 * @param element
	 */
	protected abstract void loadBean(Element element, Map<String,List<Property>> propGroup);

	/**
	 * 加载属性集
	 * @param elements
	 * @return
	 */
	private Map<String,List<Property>> loadPropGroup(List<Element> elements){
		Map<String,List<Property>> map= new HashMap<>();
		 for(Element element:elements){
			 String id=element.getAttribute("id");
			 List<Property> properties=new ArrayList<>();
			 map.put(id,properties);
			 NodeList nodeList = element.getChildNodes();
			 for(int i=0; i<=nodeList.getLength(); i++) {
				 Node node = nodeList.item(i);
				 if(!(node instanceof Element)) continue;
				 if (node.getNodeName().equals("property")) {
					 Property property = getProperty((Element) node);
					 properties.add(property);
				 }

			 }
		 }
		 return map;

	}

	/**
	 * get property tag from xml.
	 * @param element
	 * @return property
	 */
	protected Property getProperty(Element element) {
		Property property = new Property();
		String name = element.getAttribute("name");
		String query = element.getAttribute("query");
		property.setName(name);
		if(Asserts.isNotNullOrEmpty(query)){
			property.setQuery(query);
		}
		String column = element.getAttribute("column");
		property.setColumn(column);
		String isPrimaryKey = element.getAttribute("isPrimaryKey");
		property.setPrimaryKey("true".equalsIgnoreCase(isPrimaryKey));
		return property;
	}
}
